Пояснение к файлу¶
1. Файл создан в личных целях, чтобы собрать в одном месте наиболее часто используемый код в моих повседневных рабочих задачах
2. Периодически буду обновлять его по мере появления в моем личном багаже новых инструментов и методов работы с данными
3. Данный код не претендует на "идеальность", я не пытался "наводить красоту", здесь мне нужны просто рабочие методы, а уж под конкретный проект/задачу можно и красоту навести :)
4. Закомментированный код оставлен для того, чтобы показать разные способы реализации задачи
P.S. опубликованный документ "как есть" лучше, чем "красота и гениальность", лежащая в "дальнем ящике", о которой никто не знает
Я периодически забываю синтаксис написания кода/метода, поэтому хочу сделать для себя один файл-шпаргалку, в который буду постепенно собирать весь необходимый для меня, чтобы не искать по разным файлам в разных проектах.
Для подобного документа-шпаргалки есть несколько полезных ресурсов с готовыми Jupyter Notebook для Data Science:
- Data Science Handbook (by Jake VanderPlas) — это книга с примерами кода и набором методов:
- GitHub Jake VanderPlas
https://github.com/jakevdp/PythonDataScienceHandbook
- Awesome Data Science Notebooks — репозиторий с набором Jupyter Notebook для различных аспектов Data Science:
- Awesome DS Notebooks
https://github.com/jupyter-naas/awesome-notebooks
- Автор многих книг по машинному обучению, больше 10 лет выкладывает код в репозитории
Внешнее оформление тетрадки¶
Темы Jupyter Notebook¶
# !pip install jupyterthemes
## популярная темная тема
# jt -t chesterish
## Восстановить основную тему можно через
# jt -r
Комментирование, выделение рамками¶
Синий фон ячейки
<div class = "alert alert-info" style="border-left: 7px solid blue">
<b>Пояснение</b>
Текст ячейки
</div>
Зеленый фон ячейки
<div style="border:solid Green 2px; padding: 40px">
<div class="alert alert-success">
<h2> Комментарий №_<a class="tocSkip"> </h2>
<font color='green'><b> Принято </b> </font>
Текст
Оранжевый фон ячейки
<div style="border:solid Orange 2px; padding: 40px">
<div class="alert alert-warning">
<h2> Комментарий №_ <a class="tocSkip"> </h2>
<font color='Orange'><b>Некоторые рекомендации 💡:</b> </font>
Текст ячейки
Красный фон ячейки
<br/>
<div style="border:solid Red 2px; padding: 40px">
<div class="alert alert-block alert-danger">
<h2> Комментарий №_ <a class="tocSkip"></h2>
<font color='red'><b>На доработку ❌:</b> </font>
Текст замечания
</div>
Варианты оформления рамок вокруг ячейки
голубая тонкая:
<div style='border: 3px solid #67a0f549; padding: 20px'>
зеленая утолщенная:
<div style='border:solid green 5px; padding: 20px'>
Прогрессбар¶
# # прогрессбар
# import time
# from tqdm import tqdm
# mylist = [1,2,3,4,5]
# for i in tqdm(mylist):
# time.sleep(1)
Добавление в тетрадку картинок по ссылке¶
from IPython.display import Image # Библиотека для отображения картинок
display(Image(url='https://storage.googleapis.com/kaggle-datasets-images/188635/421394/be1365b1ef651ba7dc49f33588f008dc/dataset-cover.jpg?t=2019-05-09-21-51-02',
width = '') # width = 200 параметр задает ширину картинки, пустота - значение по умолчанию
)
# display(Image(filename='Python_royal_35.JPG', width = 200)) # Локальный файл
Датасет Spanish Rail Tickets Pricing - Renfe¶
Исходная ссылка на Кагл:
https://www.kaggle.com/datasets/thegurusteam/spanish-high-speed-rail-system-ticket-pricing/data
Сведения об исходных данных¶
Датасет содержит данные о стоимости билетов на поезда высокой скорости испанской железнодорожной системы Renfe. Он включает следующие параметры:
- Дата и время поездки: Указаны даты и время отправления и прибытия.
- Информация о маршруте: Содержит станции отправления и назначения.
- Класс обслуживания: Например, эконом или первый класс.
- Длительность поездки: Время в пути.
- Цена билета: Стоимость каждого билета, которая может варьироваться в зависимости от даты, времени и типа обслуживания.
- Наличие мест: Указано, сколько мест было доступно на момент бронирования.
Этот датасет можно использовать для предсказания цен на билеты с помощью моделей машинного обучения, таких как DNN (глубокие нейронные сети) или XGBoost.
- id - идентификатор
- company - компания
- origin - страна происхождения
- destination - место назначения (страна прибытия)
- departure - дата и время отправления
- arrival - дата и время прибытия
- duration - продолжительность поездки
- vehicle_type - тип транспортного средства
- vehicle_class - класс транспортного средства
- price - стоимость билета
Импорт библиотек¶
Установка нехватающих библиотек
# !pip install missingno
# !pip install chart-studio
# !pip install pykalman
# !pip install filterpy
# !pip install phik
Импорт установленных библиотек для дальнейшего анализа
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter, AutoDateLocator
import seaborn as sns
import datetime
# прогрессбар
from tqdm import tqdm
from chart_studio import plotly
import plotly.graph_objs as go
import plotly.graph_objs as go
from plotly.offline import iplot
from chart_studio import plotly as py
from plotly.offline import init_notebook_mode, iplot
import phik
from phik.report import plot_correlation_matrix
from phik import report
# для анализа временных рядов
import statsmodels.api as sm
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.graphics import tsaplots
from statsmodels.tsa.stattools import adfuller
# филтр Калмана
from filterpy.kalman import KalmanFilter
# игнорирование всплывающих уведомлений
import warnings
warnings.filterwarnings("ignore")
Чтение файла¶
# путь к расположению файла
path = 'D://MyProjects//Spanish Rail Ticket Pricing - Renfe//thegurus-opendata-renfe-trips.csv'
Стандартная загрузка всего датафрейма¶
# # Загрузка всего датафрейма без прогрессбара
# df = pd.read_csv(path, index_col=0)
Загрузка всего датафрейма с отображением прогрессбара¶
# # Устанавливаем для tqdm режим автоматического обновления
# tqdm.pandas()
# # Чтение файла с отображением прогресса
# df = pd.read_csv(path, index_col=0, iterator=True, chunksize=10000000)
# # chunksize=10000000 - разделение на блоки при чтении
# # Прогрессбар при сборке в DataFrame
# df = pd.concat([chunk.progress_apply(lambda x: x) for chunk in tqdm(df, total=38)], ignore_index=True)
Загрузка части датафрейма для быстрой отладки кода¶
# Загрузка первых nrows строк датафрейма
df = pd.read_csv(path, index_col=0, nrows=1e6)
# index_col=0 - первый столбец будет исполтзоваться в качестве индекса
# nrows=10000 - чтение только первых 10000 строк
Загрузка случайных 10 000 строк из датафрейма¶
# # Определение количества строк в файле
# total_rows = sum(1 for _ in open(path)) - 1 # -1, так как есть строка заголовка
# # Случайным образом выбираются индексы строк, которые будуд загружаться
# skip_rows = sorted(np.random.choice(range(1, total_rows + 1), total_rows - 10000, replace=False))
# # Чтение файла, пропуская выбранные строки
# df = pd.read_csv(path, index_col=0, skiprows=skip_rows)
df = df.sort_values(by='departure')
# первые строки датафрейма
df.head(3)
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | seats | meta | insert_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||
| 47799 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | NaN | {} | 2019-04-12 01:20:36 |
| 49136 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | NaN | {} | 2019-04-12 02:00:27 |
| 72165 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | NaN | {} | 2019-04-12 05:06:42 |
Первичный анализ, предобработка данных¶
Первичный анализ датафреймма¶
# общая информация о датафрейме
df.info()
<class 'pandas.core.frame.DataFrame'> Index: 1000000 entries, 47799 to 853999 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 company 1000000 non-null object 1 origin 1000000 non-null object 2 destination 1000000 non-null object 3 departure 1000000 non-null object 4 arrival 1000000 non-null object 5 duration 1000000 non-null float64 6 vehicle_type 1000000 non-null object 7 vehicle_class 996196 non-null object 8 price 693988 non-null float64 9 fare 996196 non-null object 10 seats 0 non-null float64 11 meta 1000000 non-null object 12 insert_date 1000000 non-null object dtypes: float64(3), object(10) memory usage: 106.8+ MB
# размер датафрейма (строк, столбцов)
df.shape
(1000000, 13)
Функция для вывода общей информации по датафрейму¶
def df_info(df):
'''
функция для вывода основных показателей датафреймов
'''
print("-"*100)
print('Общая информамия о датафрейме:')
print("-"*100)
print(df.info())
print("-"*100)
print('Первые 5 строк таблицы')
print("-"*100)
display(df.head(5))
print("-"*100)
print('Количество пропусков')
print("-"*100)
print(df.isnull().sum().sort_values(ascending=False))
print("-"*100)
print('Статистические данные датафрейма')
print("-"*100)
display(np.round(df.describe(), 2).T)
print("-"*100)
print('Количество дубликатов:')
print("-"*100)
display(df.duplicated().sum())
df_info(df)
---------------------------------------------------------------------------------------------------- Общая информамия о датафрейме: ---------------------------------------------------------------------------------------------------- <class 'pandas.core.frame.DataFrame'> Index: 1000000 entries, 47799 to 853999 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 company 1000000 non-null object 1 origin 1000000 non-null object 2 destination 1000000 non-null object 3 departure 1000000 non-null object 4 arrival 1000000 non-null object 5 duration 1000000 non-null float64 6 vehicle_type 1000000 non-null object 7 vehicle_class 996196 non-null object 8 price 693988 non-null float64 9 fare 996196 non-null object 10 seats 0 non-null float64 11 meta 1000000 non-null object 12 insert_date 1000000 non-null object dtypes: float64(3), object(10) memory usage: 106.8+ MB None ---------------------------------------------------------------------------------------------------- Первые 5 строк таблицы ----------------------------------------------------------------------------------------------------
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | seats | meta | insert_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||
| 47799 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | NaN | {} | 2019-04-12 01:20:36 |
| 49136 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | NaN | {} | 2019-04-12 02:00:27 |
| 72165 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | NaN | {} | 2019-04-12 05:06:42 |
| 72126 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | NaN | {} | 2019-04-12 05:06:42 |
| 28777 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | NaN | {} | 2019-04-11 23:29:00 |
---------------------------------------------------------------------------------------------------- Количество пропусков ---------------------------------------------------------------------------------------------------- seats 1000000 price 306012 vehicle_class 3804 fare 3804 company 0 origin 0 destination 0 departure 0 arrival 0 duration 0 vehicle_type 0 meta 0 insert_date 0 dtype: int64 ---------------------------------------------------------------------------------------------------- Статистические данные датафрейма ----------------------------------------------------------------------------------------------------
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| duration | 1000000.0 | 3.03 | 1.52 | 1.63 | 2.5 | 2.6 | 2.98 | 12.42 |
| price | 693988.0 | 63.56 | 25.45 | 15.45 | 45.3 | 60.3 | 76.30 | 214.20 |
| seats | 0.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
---------------------------------------------------------------------------------------------------- Количество дубликатов: ----------------------------------------------------------------------------------------------------
500653
Количество пропусков в датафрейме, заполнение их¶
# количество пропусков в столбцах
df.isnull().sum()
company 0 origin 0 destination 0 departure 0 arrival 0 duration 0 vehicle_type 0 vehicle_class 3804 price 306012 fare 3804 seats 1000000 meta 0 insert_date 0 dtype: int64
# Процент пропусков по столбцу
(df.isnull().sum() / df.shape[0]) * 100
company 0.0000 origin 0.0000 destination 0.0000 departure 0.0000 arrival 0.0000 duration 0.0000 vehicle_type 0.0000 vehicle_class 0.3804 price 30.6012 fare 0.3804 seats 100.0000 meta 0.0000 insert_date 0.0000 dtype: float64
Визуализация пропусков¶
# визуализация первых 1000 строк пропущенных данных в датафрейме
sns.heatmap(df[:1000].isna().transpose(), cmap="crest", cbar_kws={'label': 'Пропуски в данных'})
plt.show()
Уникальные значения в столбце¶
df['origin'].unique()
array(['MADRID', 'SEVILLA', 'PONFERRADA', 'VALENCIA', 'BARCELONA'],
dtype=object)
Удаление столбцов (признаков) с пропусками или ненужных столбцов¶
# Удаление ненужных столбцов
df = df.drop(columns=['seats','meta', 'insert_date'])
df.head(2)
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | |
|---|---|---|---|---|---|---|---|---|---|---|
| id | ||||||||||
| 47799 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo |
| 49136 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo |
Удаление пропусков¶
# удаление пропусков в столбце price
df.dropna(subset=['price'], inplace=True)
Можно заполнить пропуски средним значением по столбцу
# # заполнение пропусков средним значением
# df['price'].fillna(df['price'].mean(), inplace=True)
# # заполнение модой нескольких столбцов
# columns = ['train_class','fare']
# for c in columns:
# df[c].fillna(df[c].mode()[0], inplace=True)
Или полностью удалить пропуски во всем датафрейме "одним махом"
# # полное удаление всех пропусков
# df.dropna(inplace=True)
# # сброс индекса
# df = df.reset_index()
# проверка количества пропусков в столбцах
df.isnull().sum()
company 0 origin 0 destination 0 departure 0 arrival 0 duration 0 vehicle_type 0 vehicle_class 0 price 0 fare 0 dtype: int64
Переименование названий столбцов¶
# словарь для переиенования стобцов в виде "старое название - новое название"
column_dict = {
"departure" : "start_date",
"arrival" : "end_date",
"vehicle_type" : "train_type",
"vehicle_class" : "train_class"
}
# df = df.rename(columns = column_dict)
Создание дополнительных столбцов (признаков)¶
Создадание столбца времени отправления из СТРОКОВЫХ значений столбца departure
# здесь для каждой строки берутся значения с 11 по 13 элеммент (т.к. именно они являются часами отправки)
# из столбца departure, результат записывается в столбец departure_hour
df['departure_hour'] = df.apply(lambda x: int(x['departure'][11:13]), axis=1)
# преобразование значений столбца в формат datetime
datetimeFormat = '%Y-%m-%d %H:%M:%S'
def fun(a,b):
diff = datetime.datetime.strptime(b, datetimeFormat)- datetime.datetime.strptime(a, datetimeFormat)
return(diff.seconds/3600.0)
df['travel_time_in_hrs'] = df.apply(lambda x: fun(x['departure'], x['arrival']), axis=1)
# просмотр результата
df.head(2)
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | departure_hour | travel_time_in_hrs | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | ||||||||||||
| 47799 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | 5 | 3.083333 |
| 49136 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | 5 | 3.083333 |
Преобразование значений столбцов в формат даты (datetime)¶
# Изменение столбцов даты на столбцы даты и времени
for i in ['departure','arrival']:
df[i] = pd.to_datetime(df[i])
# проверка результата преобразования
df.info()
<class 'pandas.core.frame.DataFrame'> Index: 693988 entries, 47799 to 853999 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 company 693988 non-null object 1 origin 693988 non-null object 2 destination 693988 non-null object 3 departure 693988 non-null datetime64[ns] 4 arrival 693988 non-null datetime64[ns] 5 duration 693988 non-null float64 6 vehicle_type 693988 non-null object 7 vehicle_class 693988 non-null object 8 price 693988 non-null float64 9 fare 693988 non-null object 10 departure_hour 693988 non-null int64 11 travel_time_in_hrs 693988 non-null float64 dtypes: datetime64[ns](2), float64(3), int64(1), object(6) memory usage: 68.8+ MB
Выделение отдельных признаков из столбца с датой¶
Поскольку дата указана в строковом формате, ее можно разложить на разные столбцы: год, месяц, число и день недели:
for col in ['departure', 'arrival']:
date_col = pd.to_datetime(df[col])
df[col + '_hour'] = date_col.dt.hour
df[col + '_minute'] = date_col.dt.minute
df[col + '_second'] = date_col.dt.second
df[col + '_weekday'] = date_col.dt.day_of_week
df[col + '_day'] = date_col.dt.day
df[col + '_month'] = date_col.dt.month
df[col + '_year'] = date_col.dt.year
# del df[col]
df.head(2)
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | ... | departure_day | departure_month | departure_year | arrival_hour | arrival_minute | arrival_second | arrival_weekday | arrival_day | arrival_month | arrival_year | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 47799 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
| 49136 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
2 rows × 25 columns
Изменение типа данных в столбцах¶
# список столбцов для преобразования в числовой тип данных из строкового
list_columns_num = ['duration', 'price']
# список столбцов для преобразования тип данных - datetime из строкового
list_columns_date = ['departure', 'arrival']
def data_convert (dataframe, list_columns_num, list_columns_date):
'''
функция преобразования типа данных в числовые и формат даты
'''
for i in list_columns_num:
dataframe[i] = dataframe[i].astype(float)
for j in list_columns_date:
dataframe[j] = pd.to_datetime(dataframe[j])
return dataframe
# # вызов функции для преобразования типов данных в столбцах
# df = data_convert(df, list_columns_num, list_columns_date)
Выборка нужных столбцов из исходных таблиц¶
# список названий столбцов в удобном порядке
list_columns = ['origin',
'departure',
'price'
]
def df_select_columns (dataframe, list_columns):
'''
функция выбирает столбцы списка list_columns из dataframe,
возвращается датафрейм с выбранными столбцами
'''
dataframe = dataframe[list_columns]
return dataframe
# вызов функции для выборки столбцов по листу ХХХ
df_select = df_select_columns (df, list_columns)
# просмотр результата выборки
df_select.head(2)
| origin | departure | price | |
|---|---|---|---|
| id | |||
| 47799 | MADRID | 2019-04-12 05:50:00 | 85.1 |
| 49136 | MADRID | 2019-04-12 05:50:00 | 85.1 |
Формирование выборки по нескольким признакам¶
# таблица - выборка по двум признакам:
# выбираются все строки со значением MADRID столбца origin и значениями SEVILLA столбца destination
df1 = df[(df['origin']=="MADRID") & (df['destination']=="SEVILLA")]
df1.head(3)
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | ... | departure_day | departure_month | departure_year | arrival_hour | arrival_minute | arrival_second | arrival_weekday | arrival_day | arrival_month | arrival_year | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 73448 | renfe | MADRID | SEVILLA | 2019-04-12 06:20:00 | 2019-04-12 09:16:00 | 2.93 | AV City | Turista | 62.2 | Flexible | ... | 12 | 4 | 2019 | 9 | 16 | 0 | 4 | 12 | 4 | 2019 |
| 73485 | renfe | MADRID | SEVILLA | 2019-04-12 06:20:00 | 2019-04-12 09:16:00 | 2.93 | AV City | Turista | 62.2 | Flexible | ... | 12 | 4 | 2019 | 9 | 16 | 0 | 4 | 12 | 4 | 2019 |
| 21128 | renfe | MADRID | SEVILLA | 2019-04-12 06:20:00 | 2019-04-12 09:16:00 | 2.93 | AV City | Turista | 62.2 | Flexible | ... | 12 | 4 | 2019 | 9 | 16 | 0 | 4 | 12 | 4 | 2019 |
3 rows × 25 columns
f, ax = plt.subplots(figsize=(12, 2))
ax = sns.barplot(x="vehicle_type", y="price", data=df1)
plt.show()
f, ax = plt.subplots(figsize=(12, 4))
ax = sns.barplot(x="vehicle_type", y="price", data=df1, palette="coolwarm")
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')
ax.set_xlabel('Тип транспортного средства')
ax.set_ylabel('Цена')
ax.set_title('Цены на транспортные средства по типам')
# Отображение значений на барах
for p in ax.patches:
ax.annotate(f'{p.get_height():.0f}', (p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center', xytext=(0, 10), textcoords='offset points')
# Ограничение диапазона Y (по желанию)
ax.set_ylim(0, df1['price'].max() * 1.1)
plt.tight_layout()
plt.show()
Функция выборки таблицы с нужными значениями¶
def data_selection(dataframe, column, name_unique):
'''
функция для выборки данных из исходного датафрейма
по уникальным товарам name_unique в столбце column,
возвращает сформированную выборку в новый датафрейм df_unique
и название введенного уникального наименования name_unique
'''
df_unique = dataframe[(dataframe[column] == name_unique)]
return df_unique
# вызов функции выборки по уникальному значению
column_select = 'vehicle_type'
name_unique = 'AVE'
df_uniq = data_selection(df, column_select, name_unique)
df_uniq.head(2)
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | ... | departure_day | departure_month | departure_year | arrival_hour | arrival_minute | arrival_second | arrival_weekday | arrival_day | arrival_month | arrival_year | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 47799 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
| 49136 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
2 rows × 25 columns
Преобразование значений столбца в другие единицы измерения¶
df_uniq['price'] = df_uniq['price'] / 1000
df_uniq.head(2)
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | ... | departure_day | departure_month | departure_year | arrival_hour | arrival_minute | arrival_second | arrival_weekday | arrival_day | arrival_month | arrival_year | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 47799 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 0.0851 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
| 49136 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 0.0851 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
2 rows × 25 columns
Сортировка таблицы по значениям столбца¶
# сортировка таблицы по столбцу 'price'
df_sorted = df_uniq.sort_values(by='price')
df_sorted.head()
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | ... | departure_day | departure_month | departure_year | arrival_hour | arrival_minute | arrival_second | arrival_weekday | arrival_day | arrival_month | arrival_year | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 934579 | renfe | VALENCIA | MADRID | 2019-05-23 06:30:00 | 2019-05-23 08:22:00 | 1.87 | AVE | Turista | 0.02193 | Promo | ... | 23 | 5 | 2019 | 8 | 22 | 0 | 3 | 23 | 5 | 2019 |
| 993664 | renfe | VALENCIA | MADRID | 2019-05-23 06:30:00 | 2019-05-23 08:22:00 | 1.87 | AVE | Turista | 0.02193 | Promo | ... | 23 | 5 | 2019 | 8 | 22 | 0 | 3 | 23 | 5 | 2019 |
| 849128 | renfe | VALENCIA | MADRID | 2019-05-23 06:30:00 | 2019-05-23 08:22:00 | 1.87 | AVE | Turista | 0.02193 | Promo | ... | 23 | 5 | 2019 | 8 | 22 | 0 | 3 | 23 | 5 | 2019 |
| 766149 | renfe | VALENCIA | MADRID | 2019-05-23 06:30:00 | 2019-05-23 08:22:00 | 1.87 | AVE | Turista | 0.02193 | Promo | ... | 23 | 5 | 2019 | 8 | 22 | 0 | 3 | 23 | 5 | 2019 |
| 833971 | renfe | VALENCIA | MADRID | 2019-05-23 06:30:00 | 2019-05-23 08:22:00 | 1.87 | AVE | Turista | 0.02193 | Promo | ... | 23 | 5 | 2019 | 8 | 22 | 0 | 3 | 23 | 5 | 2019 |
5 rows × 25 columns
# гистограмма распределения значений
df_sorted['price'].hist(figsize=(10, 2), color='green', alpha=0.3, label='price')
plt.title('Распределение записей в таблице в зависимости от цены price')
plt.xlabel('price')
plt.ylabel('количество строк')
plt.legend()
plt.grid(True)
plt.minorticks_on()
plt.grid(which='major', linestyle='-', linewidth='0.5', color='black')
plt.grid(which='minor', linestyle=':', linewidth='0.5', color='grey')
plt.show()
Группировка по странам origin, суммирование price¶
df_groupby = df.groupby('origin')['price'].sum()
df_groupby
origin BARCELONA 6330779.56 MADRID 28520388.02 PONFERRADA 450953.60 SEVILLA 6472136.00 VALENCIA 2334064.62 Name: price, dtype: float64
EDA - исследовательский анализ данных¶
Числовая аналитика¶
Уникальные значения в столбце¶
# количество уникальных значений в столбце vehicle_type
print(len(df['vehicle_type'].unique()))
15
# первые 5 уникальных значений столбца vehicle_type
unique_vehicle_type = df['vehicle_type'].unique()[:5]
unique_vehicle_type
array(['AVE', 'REGIONAL', 'AV City', 'R. EXPRES', 'INTERCITY'],
dtype=object)
# количество записей в столбце destination
df['destination'].count()
693988
Количество уникальных значений в каждом столбце¶
for col in df.columns:
print(col, ":", df[col].unique().shape[0])
company : 1 origin : 5 destination : 5 departure : 4776 arrival : 6122 duration : 87 vehicle_type : 15 vehicle_class : 5 price : 184 fare : 4 departure_hour : 19 travel_time_in_hrs : 87 departure_minute : 26 departure_second : 1 departure_weekday : 7 departure_day : 31 departure_month : 3 departure_year : 1 arrival_hour : 18 arrival_minute : 49 arrival_second : 1 arrival_weekday : 7 arrival_day : 31 arrival_month : 3 arrival_year : 1
Дата начала и конца датафрейма¶
Проверка наличия минимальных и максимальных дат в столбцах даты и времени
print(f" started date minimum value {df.departure.min()}")
print(f" started date maximum value {df.departure.max()}")
started date minimum value 2019-04-12 05:50:00 started date maximum value 2019-06-13 16:05:00
Статистические показатели¶
# всего датафрейма
print("Основные статистические показатели:\n")
df.describe()
Основные статистические показатели:
| departure | arrival | duration | price | departure_hour | travel_time_in_hrs | departure_minute | departure_second | departure_weekday | departure_day | departure_month | departure_year | arrival_hour | arrival_minute | arrival_second | arrival_weekday | arrival_day | arrival_month | arrival_year | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 693988 | 693988 | 693988.000000 | 693988.000000 | 693988.000000 | 693988.000000 | 693988.000000 | 693988.0 | 693988.000000 | 693988.000000 | 693988.000000 | 693988.0 | 693988.000000 | 693988.000000 | 693988.0 | 693988.000000 | 693988.000000 | 693988.000000 | 693988.0 |
| mean | 2019-05-10 05:45:43.896205312 | 2019-05-10 08:43:44.320622848 | 2.966809 | 63.557759 | 13.371437 | 2.966785 | 22.363620 | 0.0 | 2.797769 | 17.882306 | 4.724824 | 2019.0 | 15.817115 | 29.747196 | 0.0 | 2.796138 | 17.881816 | 4.725383 | 2019.0 |
| min | 2019-04-12 05:50:00 | 2019-04-12 08:38:00 | 1.630000 | 15.450000 | 2.000000 | 1.633333 | 0.000000 | 0.0 | 0.000000 | 1.000000 | 4.000000 | 2019.0 | 0.000000 | 0.000000 | 0.0 | 0.000000 | 1.000000 | 4.000000 | 2019.0 |
| 25% | 2019-04-27 21:15:00 | 2019-04-28 00:02:00 | 2.500000 | 45.300000 | 9.000000 | 2.500000 | 0.000000 | 0.0 | 1.000000 | 11.000000 | 4.000000 | 2019.0 | 12.000000 | 17.000000 | 0.0 | 1.000000 | 11.000000 | 4.000000 | 2019.0 |
| 50% | 2019-05-10 12:00:00 | 2019-05-10 14:55:00 | 2.530000 | 60.300000 | 14.000000 | 2.533333 | 25.000000 | 0.0 | 3.000000 | 19.000000 | 5.000000 | 2019.0 | 16.000000 | 30.000000 | 0.0 | 3.000000 | 19.000000 | 5.000000 | 2019.0 |
| 75% | 2019-05-22 11:45:00 | 2019-05-22 14:32:00 | 2.980000 | 76.300000 | 17.000000 | 2.983333 | 40.000000 | 0.0 | 4.000000 | 25.000000 | 5.000000 | 2019.0 | 20.000000 | 40.000000 | 0.0 | 4.000000 | 25.000000 | 5.000000 | 2019.0 |
| max | 2019-06-13 16:05:00 | 2019-06-13 22:47:00 | 12.420000 | 214.200000 | 22.000000 | 12.416667 | 58.000000 | 0.0 | 6.000000 | 31.000000 | 6.000000 | 2019.0 | 23.000000 | 59.000000 | 0.0 | 6.000000 | 31.000000 | 6.000000 | 2019.0 |
| std | NaN | NaN | 1.456588 | 25.451861 | 4.681233 | 1.456366 | 17.585896 | 0.0 | 1.970651 | 8.711978 | 0.529388 | 0.0 | 4.933961 | 15.635315 | 0.0 | 1.969939 | 8.715287 | 0.529722 | 0.0 |
# отдельного столбца price
stat_column = 'price'
print(f'Статистические показатели столбца {stat_column}:')
df[stat_column].describe()
Статистические показатели столбца price:
count 693988.000000 mean 63.557759 std 25.451861 min 15.450000 25% 45.300000 50% 60.300000 75% 76.300000 max 214.200000 Name: price, dtype: float64
Графическая аналитика¶
Распределение значений выходной / рабочий день¶
def make_features(data, MAX_LAG, ROLLING_MEAN_SIZE, column):
'''функция генерации новых признаков'''
num_col = []
# день недели/месяц
data['день_недели'] = data.index.dayofweek
data['месяц'] = data.index.month
# бинарный признак для выходных дней (суббота и воскресенье)
data['выходной'] = data.index.dayofweek.isin([5, 6]).astype(int)
# Определение времени года по месяцам
data['сезон'] = data['месяц'].apply(lambda x: 'Весна' if x in [2, 3, 4, 5] else
'Лето' if x in [6, 7, 8] else
'Осень' if x in [9, 10, 11] else
'Зима')
# Создание словаря для замены чисел на названия дней недели
days_mapping = {
0: 'пн',
1: 'вт',
2: 'ср',
3: 'чт',
4: 'пт',
5: 'сб',
6: 'вс'
}
# Применение замены к столбцу 'день_недели'
data['день_недели'] = data['день_недели'].map(days_mapping)
if ROLLING_MEAN_SIZE > 0:
data['rolling_mean'] = data[column].shift().rolling(ROLLING_MEAN_SIZE).mean()
num_col.append('rolling_mean')
if MAX_LAG > 0:
for lag in range(1, MAX_LAG + 1):
data['lag_{}'.format(lag)] = data[column].shift(lag)
num_col.append('lag_{}'.format(lag))
data.dropna(inplace=True)
return data, num_col
выходной - флаг на выходной день:
- 1 - выходной
- 0 - будни
rolling_mean - скользящее среднее
lag - сдвиг на количество дней
# ширина окна сдвига, дней
MAX_LAG = 10
# ширина окна скользящего среднего, дней
ROLLING_MEAN_SIZE = 10
# # дополнительные фичи
# df_features_Up = df_Unique_product.copy(deep=True)
# df_features_Up, NUM_FEATURES = make_features(df_Unique_product, MAX_LAG, ROLLING_MEAN_SIZE, 'количество')
# # Подготовка данных для диаграммы
# labels = df_features_Up['выходной'].value_counts().index
# sizes = df_features_Up['выходной'].value_counts().values
# labels = df_features_Up['выходной'].value_counts()
# # Создание круговой диаграммы
# plt.figure(figsize=(4, 4))
# plt.pie(df_features_Up.groupby('выходной')['количество'].sum(), labels=labels, autopct='%1.1f%%', startangle=45)
# plt.title('Распределение выходных')
# plt.show()
# sum_weekend = df_features_Up.groupby('выходной')['количество'].sum()
# sum_weekend
df['origin'].unique()
array(['MADRID', 'SEVILLA', 'VALENCIA', 'PONFERRADA', 'BARCELONA'],
dtype=object)
df_filtered = df[df['origin'].isin(["MADRID", "SEVILLA"])]
# список цветов
colors = ["#FF5733", "#33C4FF", "#FF33B8", "#3380FF"]
# Настройка графика
plt.figure(figsize=(12, 6), facecolor="#f0f0f0")
# Построение временного ряда
time_series = sns.lineplot(
x='departure',
y='price',
data = df_filtered,
hue='origin',
palette=colors
)
# Настройка формата оси X для отображения дат (если departure содержит даты)
time_series.xaxis.set_major_formatter(plt.matplotlib.dates.DateFormatter('%Y-%m-%d'))
time_series.set_title("Изменение цены за билет по времени и странам", fontsize=16)
time_series.set_ylabel("Цена", fontsize=12)
time_series.set_xlabel("Дата вылета", fontsize=12)
# Перемещение легенды за пределы графика
time_series.legend(title='Страна отправления', loc='upper left', bbox_to_anchor=(1, 1))
# поворот отображения подписей
plt.xticks(rotation=45)
plt.tight_layout()
# Отображение сетки и графика
plt.grid()
plt.show()
# Создание фигуры и оси
fig, ax = plt.subplots(figsize=(12, 5))
# Построение боксплота на оси `ax`
df[:10000].boxplot(by='destination', column=['price'], grid=False, ax=ax, patch_artist=True)
# Добавление сетки
ax.grid(True, linestyle='--', linewidth=0.5)
# Добавление заголовков и подписей осей
ax.set_title('Распределение цен на билеты по пунктам назначения', fontsize=14)
ax.set_xlabel('Пункт назначения', fontsize=12)
ax.set_ylabel('Цена билета', fontsize=12)
# Поворот меток на оси X для лучшей читаемости
plt.xticks(rotation=45)
# Улучшенное размещение графика
plt.tight_layout()
# Отображение графика
plt.show()
Гистограммы распределения значений¶
# гистограмма распределения значений
df['price'].hist(figsize=(10, 2), color='green', alpha=0.5, label='price')
plt.title('Распределение записей в таблице в зависимости от цены price')
plt.xlabel('price')
plt.ylabel('количество строк')
plt.legend()
plt.grid(True)
plt.minorticks_on()
plt.grid(which='major', linestyle='-', linewidth='0.5', color='black')
plt.grid(which='minor', linestyle=':', linewidth='0.5', color='grey')
plt.show()
# # Уменьшаем разрешение графика (DPI)
# fig, ax = plt.subplots(figsize=(12, 5), dpi=200)
# ax.plot(df.index, df['price'], label='значение', color='green', alpha=0.5)
# ax.set_title('Динамика изменения price')
# ax.set_xlabel('дата')
# ax.set_ylabel('цена')
# # Устанавливаем формат и частоту меток оси X
# locator = AutoDateLocator()
# ax.xaxis.set_major_locator(locator)
# ax.xaxis.set_major_formatter(DateFormatter('%Y-%m-%d'))
# plt.xticks(rotation=90)
# ax.legend()
# plt.grid(True)
# plt.minorticks_on()
# plt.grid(which='major', linestyle='-', linewidth='0.5', color='black')
# plt.grid(which='minor', linestyle=':', linewidth='0.5', color='grey')
# plt.tight_layout()
# plt.show()
!!! Диаграммы¶
plt.figure(figsize=(11, 1))
sns.boxplot(x=df['price'])
# plt.xlim(0, 0.8)
plt.grid(True)
plt.show()
cnt_ = df['vehicle_class'].value_counts()
cnt_ = cnt_.sort_index()
fig = {
"data": [
{
"values": cnt_.values,
"labels": cnt_.index,
"domain": {"x": [0, .5]},
"name": "Percentage of journeys started and ended on same date",
"hoverinfo":"label+percent+name",
"hole": .5, # процент внутреннего круга
"type": "pie"
},],
"layout": {
"title":"Percentage of journeys started and ended on same date",
"annotations": [
{ "font": { "size": 20},
"showarrow": False,
"text": "Pie Chart",
"x": 0.50,
"y": 1
},
]
}
}
iplot(fig)
cnt_
vehicle_class Cama Turista 198 Preferente 65426 Turista 542956 Turista Plus 57018 Turista con enlace 28390 Name: count, dtype: int64
cnt_srs = df['fare'].value_counts()
trace1 = go.Bar(
x = cnt_srs.index,
y = cnt_srs.values,
marker = dict(color = 'rgba(0, 255, 200, 0.8)',
line=dict(color='rgb(0,0,0)',width=0.2)),
text = cnt_srs.index)
data = [trace1]
layout = go.Layout(title = 'Распределение Fare')
fig = go.Figure(data = data, layout = layout)
iplot(fig)
С логарифмической шкалой
cnt_srs = df['fare'].value_counts()
trace1 = go.Bar(
x=cnt_srs.index,
y=cnt_srs.values,
marker=dict(color='rgba(0, 200, 150, 0.8)', # Более мягкий цвет
line=dict(color='rgb(0,0,0)', width=0.5)),
text=cnt_srs.values,
hoverinfo='x+y+text',
width=0.8 # Увеличение ширины столбцов
)
data = [trace1]
layout = go.Layout(
title='Распределение Fare',
xaxis_title='Fare',
yaxis_title='Количество',
yaxis=dict(showgrid=True, gridcolor='rgba(200,200,200,0.5)'),
yaxis_type='log' # Логарифмическая шкала
)
fig = go.Figure(data=data, layout=layout)
iplot(fig)
cnt_srs = df['fare'].value_counts()
trace1 = go.Bar(
x=cnt_srs.values,
y=cnt_srs.index,
orientation='h',
marker=dict(color='rgba(155, 0, 100, 0.8)',
line=dict(color='rgb(0,0,0)', width=0.2)),
text=cnt_srs.index
)
data = [trace1]
layout = go.Layout(
title='Название диаграммы',
width=1000, # Устанавливаем ширину
height=400 # Устанавливаем высоту
)
fig = go.Figure(data=data, layout=layout)
iplot(fig)
cnt_srs = df['duration'].value_counts()
# Основной график с маркерами
trace1 = go.Scatter(
x=cnt_srs.index,
y=cnt_srs.values,
mode="markers",
marker=dict(size=10, color='rgba(100, 35, 55, 0.8)', line=dict(width=2, color='DarkSlateGrey')),
text=cnt_srs.values, # Отображение значений при наведении
hoverinfo='x+y+text'
)
# Данные для графика
data = [trace1]
# Настройка оформления
layout = dict(
title='Распределение продолжительности поездок',
xaxis=dict(title='Продолжительность (часы)', ticklen=5, zeroline=False, showgrid=True),
yaxis=dict(title='Количество поездок', showgrid=True)
)
fig = dict(data = data, layout = layout)
iplot(fig)
trace1 = go.Box(
y=df.price[:10000,],
name = 'Box plot of average travelling time in minutes only 50k observations',
marker = dict(
color = 'rgb(12, 12, 140)',
)
)
data = [trace1]
iplot(data)
df.groupby(['origin','vehicle_type'])['vehicle_type'].count()
origin vehicle_type
BARCELONA AVE 68560
AVE-TGV 3024
R. EXPRES 3386
MADRID ALVIA 11310
AV City 14040
AVE 348850
AVE-LD 6852
AVE-MD 3390
AVE-TGV 6634
INTERCITY 18784
LD 334
LD-MD 1918
MD-LD 2872
R. EXPRES 5272
REGIONAL 24058
TRENHOTEL 1458
PONFERRADA ALVIA 2214
LD 3062
MD-AVE 1192
MD-LD 3338
TRENHOTEL 1574
SEVILLA ALVIA 6660
AV City 10136
AVE 79574
INTERCITY 4066
LD-MD 2152
MD 878
VALENCIA ALVIA 1944
AVE 37894
INTERCITY 5764
MD-LD 2402
REGIONAL 10396
Name: vehicle_type, dtype: int64
df.head(2)
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | ... | departure_day | departure_month | departure_year | arrival_hour | arrival_minute | arrival_second | arrival_weekday | arrival_day | arrival_month | arrival_year | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 47799 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
| 49136 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
2 rows × 25 columns
cnt_srs = df.groupby('vehicle_type')['price'].agg(['mean'])
cnt_srs.columns = ["mean"]
cnt_srs['vehicle_type'] = cnt_srs.index
data = [
{
'x': cnt_srs['vehicle_type'],
'y': cnt_srs['mean'],
'mode': 'markers+text',
'text' : cnt_srs['vehicle_type'],
'textposition' : 'bottom center',
'marker': {
'color': "#f27da6",
'size': 15,
'opacity': 0.9
}
}
]
layout = go.Layout(title="Average fare prices according to Train type",
xaxis=dict(title='Train type'),
yaxis=dict(title='Average price(Euros)')
)
fig = go.Figure(data = data, layout = layout)
iplot(fig, filename='scatter0')
Количество людей, выходящих на посадку с разных станций
fig, ax = plt.subplots(figsize=(12, 3))
ax = sns.countplot(df['origin'])
plt.show()
Распределение цен на билеты
f, ax = plt.subplots(figsize=(12, 4))
ax = sns.distplot(df['price'][500:1000], rug=True)
plt.show();
import seaborn as sns
import matplotlib.pyplot as plt
# Создаем фигуру и ось
f, ax = plt.subplots(figsize=(12, 4))
# Строим гистограмму с KDE
sns.histplot(
df['price'][500:1000], kde=True, bins=30, ax=ax,
color='orange', kde_kws={'bw_adjust': 0.5}
)
# Отдельно строим rugplot
sns.rugplot(
df['price'][500:1000], ax=ax, color='green'
)
# Добавляем заголовок, подписи осей и сетку
ax.set_title('Распределение цен (для строк с 500 до 1000)')
ax.set_xlabel('Цена')
ax.set_ylabel('Частота')
ax.grid(True)
# Отображаем график
plt.show()
f, ax = plt.subplots(figsize=(12, 3))
ax = sns.boxplot(x='vehicle_class', y='price', data=df)
plt.show()
plt.figure(figsize=(12, 4))
sns.scatterplot(data=df[df['destination'] != 'MADRID'].sample(1000), x='duration',
y='price', hue='destination');
plt.title('График цен и времени отправления с указанием пункта назначения (за исключением Мадрида)');
plt.xticks(range(24))
plt.show()
В этом случае, если мы можем наблюдать, как постоянно, независимо от времени суток, пункт назначения с самым высоким тарифом-Барселона, при этом пиковые значения сохраняются в вечерние часы, в то время как, напротив, самые низкие значения обычно наблюдаются для поездов, следующих в Валенсию.
# Гистограмма с настройками
sns.displot(
df[df['destination'] == 'SEVILLA']['price'].dropna(),
bins=64,
kde=True, # Включение плотности KDE
color='skyblue', # Установка цвета
height=5, aspect=1.9 # Размер графика
)
# Добавление заголовка и подписей осей
plt.title('Гистограмма цен на билеты в Севилью', fontsize=16)
plt.xlabel('Цена билета', fontsize=12)
plt.ylabel('Количество билетов', fontsize=12)
# Отображение сетки и графика
plt.grid(True)
plt.show()
На приведенной выше диаграмме мы видим, что самый обычный тариф на поезда, следующие в Севилью, составляет чуть менее 80 евро, а большинство билетов, как правило, составляют 45-75 евро, при этом тарифы за пределами этих диапазонов встречаются гораздо реже.
print(df['destination'].value_counts())
destination MADRID 248216 BARCELONA 168050 VALENCIA 137290 SEVILLA 129332 PONFERRADA 11100 Name: count, dtype: int64
# Размер фигуры
plt.figure(figsize=(10, 5))
# Строим барчарт с горизонтальными столбцами, отсортированный по убыванию
df['destination'].value_counts().sort_values().plot(kind='barh', color='skyblue')
# Добавляем заголовок и подписи осей
plt.title('Частота билетов в зависимости от пункта назначения', fontsize=16)
plt.xlabel('Количество билетов', fontsize=12)
plt.ylabel('Пункт назначения', fontsize=12)
# Добавляем сетку для лучшей читаемости
plt.grid(axis='x', linestyle='--', alpha=0.7)
# Отображаем график
plt.show()
# Размер фигуры
plt.figure(figsize=(11, 5))
# Строим countplot, отсортированный по убыванию
sns.countplot(
data=df,
y='destination', # Используем горизонтальный график
order=df['destination'].value_counts().index, # Сортировка по убыванию
palette='coolwarm' # Приятная цветовая палитра
)
# Заголовок и подписи осей
plt.title('Частота билетов по пунктам назначения', fontsize=16)
plt.xlabel('Количество билетов', fontsize=12)
plt.ylabel('Пункт назначения', fontsize=12)
# Добавление значений на столбцы
for index, value in enumerate(df['destination'].value_counts()):
plt.text(value, index, str(value), va='center', ha='left', fontsize=10)
# Сетка по оси X для лучшей читаемости
plt.grid(axis='x', linestyle='--', alpha=0.7)
# Отображение графика
plt.show()
plt.figure(figsize=(15, 6))
df[:10000].groupby('fare')['destination'].value_counts().plot(kind='bar');
plt.title('Частота билетов в зависимости от пункта назначения и тарифа')
plt.show()
# Размер фигуры
plt.figure(figsize=(12, 6))
# Сгруппируем данные по тарифам и пунктам назначения
fare_destination_counts = df[:10000].groupby(['fare', 'destination']).size().unstack()
# Построение stacked bar графика
fare_destination_counts.plot(kind='bar', stacked=True, figsize=(12, 6), colormap='tab20')
# Заголовок и подписи осей
plt.title('Частота билетов в зависимости от пункта назначения и тарифа', fontsize=16)
plt.xlabel('Тариф', fontsize=12)
plt.ylabel('Количество билетов', fontsize=12)
# Поворот меток по оси X
plt.xticks(rotation=45)
# Легенда
plt.legend(title='Пункт назначения', bbox_to_anchor=(1.05, 1), loc='upper left')
# Сетка по оси Y
plt.grid(axis='y', linestyle='--', alpha=0.7)
# Отображение графика
plt.tight_layout()
plt.show();
<Figure size 1200x600 with 0 Axes>
# Размер графика
plt.figure(figsize=(12, 5))
# Строим график с countplot с разбивкой по тарифам
sns.countplot(x='destination', data=df[:10000], hue='fare', palette='Set2')
# Заголовок и подписи осей
plt.title('Частота билетов по пунктам назначения и тарифам', fontsize=16)
plt.xlabel('Пункт назначения', fontsize=12)
plt.ylabel('Количество билетов', fontsize=12)
# Поворот меток оси X для лучшей читаемости
plt.xticks(rotation=45, ha='right')
# Легенда вынесена за пределы графика
plt.legend(title='Тариф', bbox_to_anchor=(1.05, 1), loc='upper left')
# Сетка для оси Y
plt.grid(axis='y', linestyle='--', alpha=0.7)
# Обрезаем пространство вокруг графика для лучшего отображения
plt.tight_layout()
# Отображение графика
plt.show()
df.groupby('destination')['fare'].value_counts()
destination fare
BARCELONA Promo 137454
Flexible 25324
Adulto ida 5272
MADRID Promo 172268
Flexible 54706
Adulto ida 13782
Promo + 7460
PONFERRADA Flexible 4260
Promo + 3654
Promo 3186
SEVILLA Promo 83330
Flexible 43802
Promo + 2200
VALENCIA Promo 102720
Adulto ida 24058
Flexible 9842
Promo + 670
Name: count, dtype: int64
dataFiltr = df.query("fare not in ['Promo', 'Flexible']")
dataFiltr['fare'].unique()
array(['Adulto ida', 'Promo +'], dtype=object)
dataSample = df.sample(100)
dataSample.head(2)
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | ... | departure_day | departure_month | departure_year | arrival_hour | arrival_minute | arrival_second | arrival_weekday | arrival_day | arrival_month | arrival_year | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 865291 | renfe | BARCELONA | MADRID | 2019-05-28 18:25:00 | 2019-05-28 20:55:00 | 2.50 | AVE | Turista | 100.4 | Promo | ... | 28 | 5 | 2019 | 20 | 55 | 0 | 1 | 28 | 5 | 2019 |
| 916825 | renfe | MADRID | VALENCIA | 2019-04-22 17:48:00 | 2019-04-22 21:58:00 | 4.17 | INTERCITY | Turista | 42.9 | Flexible | ... | 22 | 4 | 2019 | 21 | 58 | 0 | 0 | 22 | 4 | 2019 |
2 rows × 25 columns
Корреляция данных¶
df_filtered
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | ... | departure_day | departure_month | departure_year | arrival_hour | arrival_minute | arrival_second | arrival_weekday | arrival_day | arrival_month | arrival_year | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 47799 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.10 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
| 49136 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.10 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
| 72165 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.10 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
| 72126 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.10 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
| 28777 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.10 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 803271 | renfe | MADRID | VALENCIA | 2019-06-13 16:05:00 | 2019-06-13 22:47:00 | 6.70 | REGIONAL | Turista | 28.35 | Adulto ida | ... | 13 | 6 | 2019 | 22 | 47 | 0 | 3 | 13 | 6 | 2019 |
| 758211 | renfe | MADRID | VALENCIA | 2019-06-13 16:05:00 | 2019-06-13 22:47:00 | 6.70 | REGIONAL | Turista | 28.35 | Adulto ida | ... | 13 | 6 | 2019 | 22 | 47 | 0 | 3 | 13 | 6 | 2019 |
| 758205 | renfe | MADRID | VALENCIA | 2019-06-13 16:05:00 | 2019-06-13 22:47:00 | 6.70 | REGIONAL | Turista | 28.35 | Adulto ida | ... | 13 | 6 | 2019 | 22 | 47 | 0 | 3 | 13 | 6 | 2019 |
| 794777 | renfe | MADRID | VALENCIA | 2019-06-13 16:05:00 | 2019-06-13 22:47:00 | 6.70 | REGIONAL | Turista | 28.35 | Adulto ida | ... | 13 | 6 | 2019 | 22 | 47 | 0 | 3 | 13 | 6 | 2019 |
| 853999 | renfe | MADRID | VALENCIA | 2019-06-13 16:05:00 | 2019-06-13 22:47:00 | 6.70 | REGIONAL | Turista | 28.35 | Adulto ida | ... | 13 | 6 | 2019 | 22 | 47 | 0 | 3 | 13 | 6 | 2019 |
549238 rows × 25 columns
# Выбираем только числовые столбцы
numeric_df = df.select_dtypes(include=[np.number])
# Вычисляем корреляцию
corr = numeric_df.corr()
# Создаем маску для верхнего треугольника
mask = np.triu(np.ones_like(corr, dtype=bool))
# Размер графика
plt.figure(figsize=(10, 8))
# Отображаем корреляционную матрицу
sns.heatmap(corr, annot=True, mask=mask, cmap="coolwarm",
vmin=-1, vmax=1, center=0,
linewidths=0.5, linecolor='gray',
fmt='.2f', annot_kws={"size": 10})
# Заголовок и подписи осей
plt.title('Корреляционная матрица числовых данных', fontsize=16)
plt.xlabel('Переменные', fontsize=12)
plt.ylabel('Переменные', fontsize=12)
# Отображение графика
plt.tight_layout()
plt.show()
# Построение таблицы (матрицы) корреляции методом phik
phik_view = df[:1000].phik_matrix()
phik_view_rounded = phik_view.round(2)
interval columns not set, guessing: ['duration', 'price', 'departure_hour', 'travel_time_in_hrs', 'departure_minute', 'departure_second', 'departure_weekday', 'departure_day', 'departure_month', 'departure_year', 'arrival_hour', 'arrival_minute', 'arrival_second', 'arrival_weekday', 'arrival_day', 'arrival_month', 'arrival_year']
phik_view_rounded
| destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | departure_hour | travel_time_in_hrs | departure_minute | arrival_hour | arrival_minute | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| destination | 1.00 | 0.96 | 1.00 | 0.94 | 0.62 | 0.50 | 0.80 | 0.74 | 0.55 | 0.94 | 0.94 | 0.57 | 0.77 |
| departure | 0.96 | 1.00 | 1.00 | 0.99 | 1.00 | 0.98 | 0.99 | 0.96 | 1.00 | 0.99 | 1.00 | 1.00 | 1.00 |
| arrival | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 | 0.98 | 0.99 | 0.99 | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| duration | 0.94 | 0.99 | 1.00 | 1.00 | 0.82 | 0.95 | 0.73 | 0.95 | 0.70 | 1.00 | 0.90 | 0.75 | 0.84 |
| vehicle_type | 0.62 | 1.00 | 1.00 | 0.82 | 1.00 | 0.48 | 0.83 | 0.78 | 0.70 | 0.82 | 0.89 | 0.69 | 0.68 |
| vehicle_class | 0.50 | 0.98 | 0.98 | 0.95 | 0.48 | 1.00 | 0.66 | 0.71 | 0.50 | 0.95 | 0.75 | 0.57 | 0.51 |
| price | 0.80 | 0.99 | 0.99 | 0.73 | 0.83 | 0.66 | 1.00 | 0.88 | 0.73 | 0.73 | 0.79 | 0.70 | 0.87 |
| fare | 0.74 | 0.96 | 0.99 | 0.95 | 0.78 | 0.71 | 0.88 | 1.00 | 0.69 | 0.95 | 0.80 | 0.68 | 0.72 |
| departure_hour | 0.55 | 1.00 | 1.00 | 0.70 | 0.70 | 0.50 | 0.73 | 0.69 | 1.00 | 0.70 | 0.72 | 0.97 | 0.74 |
| travel_time_in_hrs | 0.94 | 0.99 | 1.00 | 1.00 | 0.82 | 0.95 | 0.73 | 0.95 | 0.70 | 1.00 | 0.90 | 0.75 | 0.84 |
| departure_minute | 0.94 | 1.00 | 1.00 | 0.90 | 0.89 | 0.75 | 0.79 | 0.80 | 0.72 | 0.90 | 1.00 | 0.67 | 0.80 |
| arrival_hour | 0.57 | 1.00 | 1.00 | 0.75 | 0.69 | 0.57 | 0.70 | 0.68 | 0.97 | 0.75 | 0.67 | 1.00 | 0.74 |
| arrival_minute | 0.77 | 1.00 | 1.00 | 0.84 | 0.68 | 0.51 | 0.87 | 0.72 | 0.74 | 0.84 | 0.80 | 0.74 | 1.00 |
# Графическое представление матрицы корреляции методом phik
plt.figure(figsize=(15, 8))
sns.heatmap(phik_view_rounded,
annot=True,
cmap="crest",
vmin=0,
vmax=1,
center=0.5, # Поставим центр на 0.5 для наглядности
fmt='.2f', # Формат для аннотаций
linewidths=0.5,
linecolor='gray',
cbar_kws={'shrink': 0.8, 'label': 'Phik Correlation'}, # Настройка цветовой шкалы
annot_kws={"size": 10})
# Заголовок и подписи осей
plt.title("Корреляция признаков методом phik", fontsize=16)
plt.xlabel('Признаки', fontsize=12)
plt.ylabel('Признаки', fontsize=12)
# Плотное размещение элементов
plt.tight_layout()
plt.show()
Агрегация данных: один признак под разными углами¶
Например:
- цена билета с выборкой по одной стране в зависимости от дат
- цена билета за выбранный год
- средня цена билета по разным странам
- максимум, минимум по разным странам
df2 = df[(df['origin']=="MADRID") & (df['destination']=="BARCELONA")]
df2.head(3)
| company | origin | destination | departure | arrival | duration | vehicle_type | vehicle_class | price | fare | ... | departure_day | departure_month | departure_year | arrival_hour | arrival_minute | arrival_second | arrival_weekday | arrival_day | arrival_month | arrival_year | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 47799 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
| 49136 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
| 72165 | renfe | MADRID | BARCELONA | 2019-04-12 05:50:00 | 2019-04-12 08:55:00 | 3.08 | AVE | Turista | 85.1 | Promo | ... | 12 | 4 | 2019 | 8 | 55 | 0 | 4 | 12 | 4 | 2019 |
3 rows × 25 columns
Аналитика временного ряда¶
Создание временного ряда¶
# Создаем новый датафрейм с выбранными столбцами и устанавливаем 'departure' как индекс
selected_columns = ['price', 'duration'] # выборка нескольких столбцов датафрейма
df_ts = df.set_index('departure')[selected_columns]
df_ts.head()
| price | duration | |
|---|---|---|
| departure | ||
| 2019-04-12 05:50:00 | 85.1 | 3.08 |
| 2019-04-12 05:50:00 | 85.1 | 3.08 |
| 2019-04-12 05:50:00 | 85.1 | 3.08 |
| 2019-04-12 05:50:00 | 85.1 | 3.08 |
| 2019-04-12 05:50:00 | 85.1 | 3.08 |
Либо в таком варианте, дата отдельным столбцом, не в индексе
# Создаем новый датафрейм с выбранными столбцами и устанавливаем 'departure' как индекс
selected_columns = ['departure', 'price', 'duration'] # выборка нескольких столбцов датафрейма
df_ts = df[selected_columns]
df_ts.head()
| departure | price | duration | |
|---|---|---|---|
| id | |||
| 47799 | 2019-04-12 05:50:00 | 85.1 | 3.08 |
| 49136 | 2019-04-12 05:50:00 | 85.1 | 3.08 |
| 72165 | 2019-04-12 05:50:00 | 85.1 | 3.08 |
| 72126 | 2019-04-12 05:50:00 | 85.1 | 3.08 |
| 28777 | 2019-04-12 05:50:00 | 85.1 | 3.08 |
# Извлекаем только дату (без времени)
df_ts['date'] = df_ts['departure'].dt.date
# Группируем по дате и суммируем значения в столбце 'price'
grouped_df = df_ts.groupby('date')['price'].sum().reset_index()
grouped_df.head(10)
| date | price | |
|---|---|---|
| 0 | 2019-04-12 | 131198.30 |
| 1 | 2019-04-13 | 101528.50 |
| 2 | 2019-04-14 | 432077.20 |
| 3 | 2019-04-15 | 818184.10 |
| 4 | 2019-04-16 | 979696.00 |
| 5 | 2019-04-17 | 926398.32 |
| 6 | 2019-04-18 | 852768.20 |
| 7 | 2019-04-19 | 616540.50 |
| 8 | 2019-04-20 | 573457.90 |
| 9 | 2019-04-21 | 909054.90 |
Проверка временного ряда на последовательность дат¶
# # Создаем новый датафрейм с выбранными столбцами и устанавливаем 'departure' как индекс
# selected_columns = ['price', 'duration', 'departure_hour'] # выборка нескольких столбцов датафрейма
# df_ts = df.set_index('departure')[selected_columns]
# # Проверяем, что даты в индексе монотонно возрастают
# if df_ts.index.is_monotonic_increasing:
# print("Даты идут по возрастанию.")
# else:
# print("Даты не идут по возрастанию.")
# # Проверяем на пропуски
# full_range = pd.date_range(start=df_ts.index.min(), end=df_ts.index.max(), freq='D')
# missing_dates = full_range.difference(df_ts.index)
# if len(missing_dates) > 0:
# print("Пропущенные даты:", missing_dates)
# else:
# print("Даты последовательны, пропусков нет.")
# Проверяем, что даты в индексе монотонно возрастают
if grouped_df['date'].is_monotonic_increasing:
print("Даты идут по возрастанию.")
else:
print("Даты не идут по возрастанию.")
Даты идут по возрастанию.
# Проверяем на пропуски
full_range = pd.date_range(start=grouped_df['date'].min(), end=grouped_df['date'].max(), freq='D')
missing_dates = full_range.difference(grouped_df['date'])
if len(missing_dates) > 0:
print("Пропущенные даты:", missing_dates)
else:
print("Даты последовательны, пропусков нет.")
Даты последовательны, пропусков нет.
Восстановление временного ряда, если даты непоследовательны¶
def time_series_recovery(time_series):
'''
функция для создания восстановленного (равномерного) временного ряда,
т.е. добавляются пропущенные даты, в добавленных строках "Количество" заполняются нулями
'''
time_series_recovery = time_series.reindex(
pd.date_range(start=time_series.index.min(),
end=time_series.index.max(), freq='D'), fill_value=0)
return time_series_recovery
df_ts_2 = grouped_df.set_index('date')
df_ts_2[:5]
| price | |
|---|---|
| date | |
| 2019-04-12 | 131198.3 |
| 2019-04-13 | 101528.5 |
| 2019-04-14 | 432077.2 |
| 2019-04-15 | 818184.1 |
| 2019-04-16 | 979696.0 |
df_ts_2_groupby = df_ts.groupby('departure')['price'].sum()
df_ts_2_groupby.head(10)
departure 2019-04-12 05:50:00 1702.0 2019-04-12 06:08:00 623.7 2019-04-12 06:20:00 248.8 2019-04-12 06:45:00 1315.8 2019-04-12 07:00:00 3673.2 2019-04-12 07:15:00 951.5 2019-04-12 07:30:00 2417.9 2019-04-12 08:00:00 4013.2 2019-04-12 08:30:00 851.0 2019-04-12 09:00:00 1373.4 Name: price, dtype: float64
# вызов функции для создания восстановленного (равномерого) временного ряда
ts_recovery = time_series_recovery(df_ts_2)
ts_recovery.head(10)
| price | |
|---|---|
| 2019-04-12 | 131198.30 |
| 2019-04-13 | 101528.50 |
| 2019-04-14 | 432077.20 |
| 2019-04-15 | 818184.10 |
| 2019-04-16 | 979696.00 |
| 2019-04-17 | 926398.32 |
| 2019-04-18 | 852768.20 |
| 2019-04-19 | 616540.50 |
| 2019-04-20 | 573457.90 |
| 2019-04-21 | 909054.90 |
Проверка временного ряда на стационарность¶
# Проверка стационарности временного ряда
result = adfuller(grouped_df['price'])
print("\nADF статистика:", result[0])
print("p-value:", result[1])
print("Критические значения:")
for key, value in result[4].items():
print(f"\t{key}: {value}")
if result[1] < 0.05:
print("\nРяд стационарен (отвергается гипотеза о нестационарности)")
else:
print("\nРяд нестационарен (не удается отвергнуть гипотезу о нестационарности)")
ADF статистика: -0.9207214065097787 p-value: 0.7810719962973687 Критические значения: 1%: -3.5552728880540942 5%: -2.9157312396694217 10%: -2.5956695041322315 Ряд нестационарен (не удается отвергнуть гипотезу о нестационарности)
Сезонная декомпозиция¶
df_ts_resemp = df_ts.copy()
df_ts_resemp = df_ts_resemp.set_index('departure')
df_ts_resemp = df_ts_resemp['price']
df_ts_resemp
departure
2019-04-12 05:50:00 85.10
2019-04-12 05:50:00 85.10
2019-04-12 05:50:00 85.10
2019-04-12 05:50:00 85.10
2019-04-12 05:50:00 85.10
...
2019-06-13 16:05:00 28.35
2019-06-13 16:05:00 28.35
2019-06-13 16:05:00 28.35
2019-06-13 16:05:00 28.35
2019-06-13 16:05:00 28.35
Name: price, Length: 693988, dtype: float64
# измененим интервал значений временного ряда на часовой таймфрем
df_ts_resemp_H = df_ts_resemp.resample('1H').sum()
print(f'Количество строк после ресемплинга на таймфрейм в 1ч: {df_ts_resemp_H.shape[0]}')
Количество строк после ресемплинга на таймфрейм в 1ч: 1500
print('Начало временного интервала:', df_ts_resemp_H.index[0])
print('Конец временного интервала:', df_ts_resemp_H.index[-1])
print('Общее количество дней:',df_ts_resemp_H.index.max() - df_ts_resemp_H.index.min())
Начало временного интервала: 2019-04-12 05:00:00 Конец временного интервала: 2019-06-13 16:00:00 Общее количество дней: 62 days 11:00:00
df_ts_resemp_H = df_ts_resemp.copy()
df_ts_resemp_H = df_ts_resemp_H.resample('1H').sum()
df_ts_resemp_D = df_ts_resemp.copy()
df_ts_resemp_D = df_ts_resemp_D.resample('1D').sum()
df_ts_resemp_W = df_ts_resemp.copy()
df_ts_resemp_W = df_ts_resemp_W.resample('1W').sum()
# print(df_ts_resemp_D.index.is_monotonic)
# общий график за весь промежуток времени
df_ts_resemp_H.plot(figsize=(12, 3))
plt.legend()
plt.title('Изменение price во времени')
plt.xlabel('Временной интервал')
plt.ylabel('price')
plt.show;
# график заказов такси за 10 дней
plt.figure(figsize=(11, 3))
df_ts_resemp_H['2019-05-15':'2019-05-25'].plot(figsize=(12, 3))
plt.legend()
plt.title('Количество заказов на временном интервале: 10 дней')
plt.xlabel('Временной интервал')
plt.ylabel('Количество заказов')
plt.show;
Скользящие средние¶
# построим скользящие средние с размером окна = 24ч
plt.figure(figsize=(12, 5))
plt.plot(df_ts_resemp_H, label = 'число заказов в час')
plt.plot(df_ts_resemp_H.rolling(24).mean(), label = 'скользящее среднее за сутки')
plt.plot(df_ts_resemp_H.rolling(24).std(), label = 'скользящее стандартное отклонение', color='green')
plt.legend()
plt.title('Скользящее среднее')
plt.xlabel('Временной интервал')
plt.ylabel('Количество заказов')
plt.show;
Тренды и сезонность¶
# разделим на тренды функцией seasonal_decompose
decomposed_h = seasonal_decompose(df_ts_resemp_H)
plt.figure(figsize=(11, 9))
plt.subplot(311)
# Чтобы график корректно отобразился,
# указываем его оси ax, равными plt.gca() (англ. get current axis, получить текущие оси)
decomposed_h.trend.plot(ax=plt.gca())
plt.title('Trend')
plt.subplot(312)
decomposed_h.seasonal.plot(ax=plt.gca())
plt.title('Seasonality')
plt.subplot(313)
decomposed_h.resid.plot(ax=plt.gca())
plt.title('Residuals')
plt.tight_layout()
decomp_df = seasonal_decompose(df_ts_resemp_H, period=30) # , model='additive', period=30 Период сезонности (можно изменить)
trend = decomp_df.trend
seasonal = decomp_df.seasonal
residual = decomp_df.resid
decomp_df
<statsmodels.tsa.seasonal.DecomposeResult at 0x1cd01c881f0>
plt.figure(figsize=(10, 8))
plt.subplot(411)
plt.plot(df_ts_resemp_H, color = 'green', label='price')
plt.legend(loc='best')
plt.subplot(412)
plt.plot(trend, label='Trend')
plt.legend(loc='best')
plt.subplot(413)
plt.plot(seasonal, label='Seasonality')
plt.legend(loc='best')
plt.subplot(414)
plt.plot(residual, label='Residuals')
plt.legend(loc='best')
plt.tight_layout()
# функции автокорреляции и частичной автокорреляции
fig, ax = plt.subplots(2, figsize=(12, 8))
plot_acf(df_ts_resemp_H, lags=90, ax=ax[0])
plot_pacf(df_ts_resemp_H, lags=90, ax=ax[1])
ax[0].grid(True)
ax[1].grid(True)
plt.show()
Преобразование Фурье¶
# выделяем временной ряд
time_series_1 = df_ts_resemp_H
time_series_1
departure
2019-04-12 05:00:00 1702.0
2019-04-12 06:00:00 2188.3
2019-04-12 07:00:00 7042.6
2019-04-12 08:00:00 4864.2
2019-04-12 09:00:00 5724.8
...
2019-06-13 12:00:00 963.9
2019-06-13 13:00:00 0.0
2019-06-13 14:00:00 1077.3
2019-06-13 15:00:00 0.0
2019-06-13 16:00:00 963.9
Freq: h, Name: price, Length: 1500, dtype: float64
# Преобразование Фурье
fft_result = np.fft.fft(time_series_1)
# Получение амплитуд спектра
amplitudes = np.abs(fft_result)
# Получение частот (циклов в день)
N = len(time_series_1)
freqs = np.fft.fftfreq(N)
# Оставляем только положительную часть спектра
amplitudes_pos = amplitudes[:len(amplitudes)//2]
freqs_pos = freqs[:len(freqs)//2]
# Находим 5 наиболее значимых частот, без первого (очень большого) значения
top_freq_indices = np.argsort(amplitudes_pos)[::-1][1:6]
# Выводим дни периодичности (сезонности)
periods = 1 / freqs_pos[top_freq_indices]
print("Наиболее значимые частоты и соответствующие им дни периодичности (сезонности):")
for i, period in enumerate(periods):
print(f"Частота: {freqs_pos[top_freq_indices[i]]:.4f} циклов/день, Дни периодичности: {period:.2f} дней")
Наиболее значимые частоты и соответствующие им дни периодичности (сезонности): Частота: 0.0413 циклов/день, Дни периодичности: 24.19 дней Частота: 0.0420 циклов/день, Дни периодичности: 23.81 дней Частота: 0.0833 циклов/день, Дни периодичности: 12.00 дней Частота: 0.0007 циклов/день, Дни периодичности: 1500.00 дней Частота: 0.0013 циклов/день, Дни периодичности: 750.00 дней
# Визуализация амплитуд спектра
plt.figure(figsize=(10, 5))
plt.plot(freqs_pos[1:], amplitudes_pos[1:], color = 'green')
plt.title('Амплитудный спектр временного ряда без первого значения')
plt.xlabel('Частота (циклов/день)')
plt.ylabel('Амплитуда')
plt.grid(True)
plt.show()
Фильтр Савицкого-Голея¶
from scipy.signal import savgol_filter
# Применяем фильтр Савицкого-Голея для удаления шума
smoothed_time_series = savgol_filter(time_series_1, window_length=5, polyorder=2)
# график
plt.figure(figsize=(12, 4))
plt.plot(time_series_1.index, time_series_1, label='Исходный временной ряд')
plt.plot(time_series_1.index, smoothed_time_series, label='Очищенный от шума', linestyle='--')
plt.title('Применение фильтра Савицкого-Голея')
plt.legend()
plt.show()
Фильтр Калмана¶
def kalman_filter(time_series):
# Создаем объект фильтра Калмана
kf = KalmanFilter(dim_x=1, dim_z=1)
# Инициализируем состояние и ковариацию
kf.x = np.array([[time_series[0]]]) # начальное состояние
kf.P *= 1000 # начальная ковариация
# Определяем матрицы перехода и наблюдения
kf.F = np.array([[1]]) # матрица перехода
kf.H = np.array([[1]]) # матрица наблюдения
# Устанавливаем ковариации шумов
kf.R *= 0.01 # ковариация измерения
kf.Q *= 0.01 # ковариация процесса
# Применяем фильтр Калмана к временному ряду
filtered_values = []
for measurement in time_series:
kf.predict() # предсказываем следующее состояние
kf.update(measurement) # корректируем состояние на основе измерения
filtered_values.append(kf.x[0, 0]) # добавляем скорректированное значение в список
return filtered_values
filtered_series = kalman_filter(time_series_1)
# график
plt.figure(figsize=(12, 4))
plt.plot(time_series_1.index, time_series_1, label='Исходный временной ряд')
plt.plot(time_series_1.index, filtered_series, label='Очищенный от шума', linestyle='--')
plt.title('Применение фильтра Калмана')
plt.legend()
plt.show()
Сохранение выборки в файл¶
# # сохранение таблицы в файл
# path = r'D:\MyProjects\save_data\df_ts.xlsx'
# df_ts[:100].to_excel(path, index=True)
# # [:100] - сохранение только первых 100 строк для уменьшения разммера файла